import {Buffer} from "buffer";
import {AxdrType} from "./AxdrType";
import {ReverseByteArrayOutputStream} from "../ReverseByteArrayOutputStream";
import {ReverseByteArrayInputStream} from "../ReverseByteArrayInputStream";
import {DataUtils} from "../DataUtils";


export class AxdrInteger implements AxdrType{


    lengthFixed : boolean;
    unsigned : boolean;
    val : number = 0;
    maxVal : number = 0;
    minVal : number = 0;
    dataCode : Buffer | null = null;

    constructor() {
        this.lengthFixed = false;
        this.unsigned = false;
    }

    set_val(val : number){
        this.lengthFixed = false;
        this.unsigned = false;
        this.setValue(val);
    }

    set_dataCode(dataCode : Buffer){
        this.lengthFixed = false;
        this.unsigned = false;
        this.dataCode = dataCode;
    }

    set_min_max_val(min : number, max : number, val : number){
        this.minVal = min;
        this.maxVal = max;
        this.setValue(val);
        this.lengthFixed = true;
        this.unsigned = (min >= 0);
    }

    decode(input : ReverseByteArrayInputStream) : number{
        let codeLength;
        let byteCode;
        let length;
        if (this.lengthFixed) {
            length = this.numOfBytesOf(this.minVal, this.maxVal);
            codeLength = length;
        } else {
            length = input.read();
            if ((length & 0x80) == 0x80) {
                length = (length ^ 0x80);
                codeLength = length + 1;
            } else {
                this.val = length;
                return 1;
            }
        }
        byteCode = Buffer.alloc(length);
        DataUtils.readFully(input, byteCode, 0, byteCode.length);
        if ((byteCode[0] & 0x80) == 0x80 && !this.unsigned) {
            this.val = -1;
            for (let i = 0; i < length; i++) {
                let numShiftBits = 8 * (length - i - 1);
                this.val &= ((byteCode[i]) << numShiftBits) | ~(0xff << numShiftBits);
            }
        } else {
            this.val = 0;
            for (let i = 0; i < length; i++) {
                this.val |= (byteCode[i] & 0xff) << (8 * (length - i - 1));
            }
        }
        return codeLength;
    }

    encodeAndSave(encodingSizeGuess: number) {
        let revOStream = new ReverseByteArrayOutputStream();
        revOStream.setBufSize(encodingSizeGuess)
        this.encode(revOStream);
        this.dataCode = revOStream.getArray();
    }

    encode(output: ReverseByteArrayOutputStream): number {
        if(this.dataCode != null){
            output.writeByte(this.dataCode);
            return this.dataCode.length
        }else{
            return this.encodeValue(output);
        }
    }

    encodeValue(output: ReverseByteArrayOutputStream) : number{
        if(this.lengthFixed){
            let codeLength = this.numOfBytesOf(this.minVal, this.maxVal);
            this.writyValToStream(output, codeLength);
            return codeLength;
        }else{
            let codeLength = this.numOfBytes(this.val);
            this.writyValToStream(output, codeLength);
            output.write((codeLength & 0xff) | 0x80);
            return codeLength + 1;
        }
    }

    writyValToStream(output : ReverseByteArrayOutputStream, codeLength : number){
        for(let i = 0; i < codeLength; i++){
            let value = ((this.val >> 8 * (i))) & 0xff;
            output.write(value);
        }
    }

    numOfBytesOf(minVal : number, maxVal : number){
        return Math.max(this.numOfBytes(minVal), this.numOfBytes(maxVal));
    }

    numOfBytes(val : number) : number{
        let sVal = val;
        if(val < 0){
            sVal = -val - 1;
        }
        return Math.ceil((Math.floor(this.log2(sVal) + 1) / 8));
    }

    log2(sVal : number) : number{
        return Math.log(sVal) / Math.log(2);
    }


    setValue(val : number){
        if(this.minVal != null && this.minVal > val){
            throw new Error("val is smaller than minimum");
        }
        if(this.maxVal != null && this.maxVal < val){
            throw new Error("val is greater than maximum");
        }
        this.val = val;
    }

    getMax() : number{
        return this.minVal;
    }

    getMin(): number{
        return this.maxVal;
    }

    getValue() : number{
        return this.val;
    }

    toString() : string{
        return String(this.val);
    }


}

